/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{

    [TestFixture(Description = "")]
    public class C940002 : ValidationTest
    {

        //      Implement a counting semaphore type that can be initialized to a 
        //      specific number of available resources.  Declare an entry for 
        //      requesting a resource and a procedure for releasing it.  Declare an 
        //      object of this type, initialized to two resources.  Declare and start 
        //      three tasks each of which asks for a resource.  Verify that only two 
        //      resources are granted and that the last task in is queued.


        const int MaxResources = 2;


        private static Semaphore resource;
        private static Counter counter;
        private static HoldLock holdLock;


        private static void SpinLock()
        {
            while (holdLock.Locked())
                Ada.Delay(Impdef.MinimumTaskSwitch);

            holdLock.Lock();
        }


        private class Semaphore : Protected
        {

            private int CurrentlyAvailable;


            [Entry]
            internal Action Request { get; private set; }


            [ReadWriteOperation]
            internal Action Release { get; private set; }


            [ReadOnlyOperation]
            internal Func<int> Available { get; private set; }


            internal Semaphore(int resourcesAvailabe)
            {
                CurrentlyAvailable = resourcesAvailabe;

                // when granted, secures a resource
                Entry(Request,
                    When: () => CurrentlyAvailable > 0,
                Is: () =>
                {
                    CurrentlyAvailable -= 1;
                });

                // when called, releases a resource
                ReadWriteOperation(Release, Is: () =>
                {
                    CurrentlyAvailable += 1;
                });

                // returns number of available resources
                ReadOnlyOperation(Available, Is: () =>
                {
                    return CurrentlyAvailable;
                });
            }

        }


        private class Counter : Protected
        {

            private new int Count = 0;


            [ReadWriteOperation]
            internal Action Increment { get; private set; }


            [ReadWriteOperation]
            internal Action Decrement { get; private set; }


            [ReadOnlyOperation]
            internal Func<int> Number { get; private set; }


            public Counter()
            {
                ReadWriteOperation(Increment, Is: () =>
                {
                    Count += 1;
                });

                ReadWriteOperation(Decrement, Is: () =>
                {
                    Count -= 1;
                });

                ReadOnlyOperation(Number, Is: () =>
                {
                    return Count;
                });
            }

        }


        private class HoldLock : Protected
        {

            private bool LockState = true; // starts out locked


            [ReadWriteOperation]
            internal Action Lock { get; private set; }


            [ReadWriteOperation]
            internal Action Unlock { get; private set; }


            [ReadOnlyOperation]
            internal Func<bool> Locked { get; private set; }


            public HoldLock()
            {
                ReadWriteOperation(Lock, Is: () =>
                {
                    LockState = true;
                });

                ReadWriteOperation(Unlock, Is: () =>
                {
                    LockState = false;
                });

                ReadOnlyOperation(Locked, Is: () =>
                {
                    return LockState;
                });
            }

        }


        private class RequestingTask : ValidationTestTask
        {

            [Entry]
            internal Action Done { get; private set; }


            internal RequestingTask(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                // request a resource. if resource is not available, task will be queued to wait
                resource.Request();

                // add to count of resources obtained
                counter.Increment();

                // and unlock Lock - system is stable; status may now be queried
                holdLock.Unlock();

                // hold resource until Done is called
                Accept(Done, () =>
                {
                    // release the resource and
                    resource.Release();
                    // note release
                    counter.Decrement();
                });
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Unexpected exception raised in task");
                Result.TestFailed();
            }
        }


        [Test(Description =
            "Check that a protected object provides coordinated access to shared " +
            "data.  Check that it can implement a semaphore-like construct using a " +
            "parameterless procedure which allows a specific maximum number of tasks " +
            "to run and excludes all others ")]
        public void Test()
        {
            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();
            RequestingTask task1;
            RequestingTask task2;
            RequestingTask task3;

            Ada.Declare(() =>
            {
                resource = new Semaphore(MaxResources);
                counter = new Counter();
                holdLock = new HoldLock();

                // Setup text expectations

                Expect.Call(result.TestFailed).Repeat.Never();
                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                if (counter.Number() != 0 || resource.Available() != 2)
                {
                    Console.WriteLine("Wrong initial conditions");
                    result.TestFailed();
                }

                task1 = new RequestingTask(result);
                task1.Activate();

                // newly allocated task requests resource; request for resource should be granted
                //task1.Start();

                // ensure that task obtains resource
                SpinLock();

                // Task 1 waiting for call to Done, One resource assigned to task 1, One resource still available

                if (counter.Number() != 1 || resource.Available() != 1)
                {
                    Console.WriteLine("Resource not assigned to task 1");
                    result.TestFailed();
                }

                task2 = new RequestingTask(result);
                task2.Activate();
                // newly allocated task requests resource; request for resource should be granted
                //task2.Start();

                // ensure that task obtains resource
                SpinLock();

                // Task 1 waiting for call to Done, 
                // Task 2 waiting for call to Done, 
                // Resources held by tasks 1 and 2, No resource available

                if (counter.Number() != 2 || resource.Available() != 0)
                {
                    Console.WriteLine("Resource not assigned to task 2");
                    result.TestFailed();
                }

                task3 = new RequestingTask(result);
                task3.Activate();
                
                // newly allocated task requests resource; request for resource should be denied and task
                // queued to wait for next available resource
                //task3.Start();

                // Task 1 releases resource and lock. Resource should be given to queued task
                task1.Done();

                // ensure that resource is released
                SpinLock();

                // Task 1 holds no resource. One resource still assigned to task 2.
                // One resource assigned to task 3. No resources available

                if (counter.Number() != 2 || resource.Available() != 0)
                {
                    Console.WriteLine("Resource not properly released/assigned to task 3");
                    result.TestFailed();
                }

                // Task 2 releases resource and lock. No outstanding request for resource
                task2.Done();

                // Task 1 & 2 hold no resource. One resource assigned to task 3
                // One resource available

                if (counter.Number() != 1 || resource.Available() != 1)
                {
                    Console.WriteLine("Resource not properly released from task 2");
                    result.TestFailed();
                }

                // Task 3 releases resource and lock.
                task3.Done();

                // All resources released. All tasks terminated (or close)
                // Two resources available

                if (counter.Number() != 0 || resource.Available() != 2)
                {
                    Console.WriteLine("Resource not properly released from task 3");
                    result.TestFailed();
                }
            });
        }

    }

}
